package org.lee.app.mediator;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.builder.xml.XMLMapperEntityResolver;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.dom4j.tree.DefaultText;
import org.xml.sax.InputSource;

import java.io.IOException;
import java.io.Reader;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * SqlSessionFactoryBuilder
 * 会话工厂构建器
 *
 * @author dralee
 * @date 2021-11-23 15:22:57
 **/
@Slf4j
public class SqlSessionFactoryBuilder {

    public DefaultSqlSessionFactory build(Reader reader) {
        SAXReader saxReader = new SAXReader();
        try {
            saxReader.setEntityResolver(new XMLMapperEntityResolver());
            Document document = saxReader.read(new InputSource(reader));
            Configuration configuration = parseConfiguration(document.getRootElement());
            return new DefaultSqlSessionFactory(configuration);
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        return null;
    }

    private Configuration parseConfiguration(Element root) {
        Configuration configuration = new Configuration();
        configuration.setDataSource(datasource(root.selectNodes("//dataSource")));
        configuration.setConnection(connection(configuration.dataSource));
        configuration.setMapperElement(mapperElement(root.selectNodes("mappers")));
        return configuration;
    }

    private Map<String, XNode> mapperElement(List<Node> mappers) {
        Map<String, XNode> mapper = new HashMap<>();

        Element element = (Element) mappers.get(0);
        Pattern pattern = Pattern.compile("(#\\{(.*?)})");
        for (Object obj : element.content()) {
            if (obj instanceof DefaultText) {
                continue;
            }
            Element e = (Element) obj;
            String resource = e.attributeValue("resource");
            try {
                Reader reader = Resources.getResourceAsReader(resource);
                SAXReader saxReader = new SAXReader();
                Document document = saxReader.read(new InputSource(reader));
                Element root = document.getRootElement();
                String namespace = root.attributeValue("namespace");

                List<Node> selectNodes = root.selectNodes("select");
                for (Node node : selectNodes) {
                    Element eNode = (Element) node;
                    String id = eNode.attributeValue("id");
                    String parameterType = eNode.attributeValue("parameterType");
                    String resultType = eNode.attributeValue("resultType");
                    String sql = eNode.getText();
                    // ?匹配
                    Map<Integer, String> parameter = new HashMap<>();
                    Matcher matcher = pattern.matcher(sql);
                    for (int i = 1; matcher.find(); ++i) {
                        String holder = matcher.group(1);
                        String name = matcher.group(2);
                        parameter.put(i, name);
                        sql = sql.replace(holder, "?");
                    }
                    mapper.put(namespace.concat(".").concat(id), XNode.builder()
                            .namespace(namespace)
                            .id(id).parameterType(parameterType)
                            .resultType(resultType)
                            .sql(sql).parameter(parameter)
                            .build());
                }
            } catch (IOException | DocumentException ex) {
                log.error("mapperElement error: {}", ex);
            }
        }
        return mapper;
    }

    private Connection connection(Map<String, String> dataSource) {
        try {
            Class.forName(dataSource.get("driver"));
            return DriverManager.getConnection(dataSource.get("url"), dataSource.get("username"), dataSource.get("password"));
        } catch (ClassNotFoundException | SQLException e) {
            log.error("connection error: {}", e);
        }
        return null;
    }

    private Map<String, String> datasource(List<Node> nodes) {
        Map<String, String> dataSource = new HashMap<>();
        Element element = (Element) nodes.get(0);
        List content = element.content();
        for (Object obj : content) {
            if (obj instanceof DefaultText) {
                continue;
            }
            Element e = (Element) obj;
            String name = e.attributeValue("name");
            String value = e.attributeValue("value");
            dataSource.put(name, value);
        }
        return dataSource;
    }
}
